import os
import uuid
import logging
import math
import random
from typing import Dict, Any, List, Tuple, Optional, Union
from PIL import Image, ImageDraw, ImageFont
import numpy as np
from moviepy.editor import VideoFileClip, CompositeVideoClip, ColorClip, ImageClip
from config.base_config import TIME_BASE_CONVERSION
from .s3_service import S3Service
from sdk.metadata.animation_meta import Text_intro, Text_outro, Text_loop_anim

logger = logging.getLogger('video_preview')


class VideoPreviewService:
    """视频预览生成服务"""

    @staticmethod
    def find_font(font_size):
        font_path = None
        # 尝试加载默认字体
        try:
            # 尝试查找系统字体
            if os.name == 'nt':  # Windows
                potential_fonts = [
                    os.path.join(os.environ['WINDIR'],
                                 'Fonts', 'simhei.ttf'),  # 黑体
                    os.path.join(os.environ['WINDIR'],
                                 'Fonts', 'simsun.ttc'),  # 宋体
                    os.path.join(os.environ['WINDIR'],
                                 'Fonts', 'msyh.ttc'),    # 微软雅黑
                    os.path.join(os.environ['WINDIR'],
                                 'Fonts', 'arial.ttf'),   # Arial
                ]

            # 找到第一个存在的字体
            for font_path in potential_fonts:
                if os.path.exists(font_path):
                    logger.info(f"使用系统字体: {font_path}")
                    break
            else:
                font_path = None

            if font_path:
                font = ImageFont.truetype(font_path, font_size)
                logger.info(f"成功加载字体: {font_path}")
            else:
                # 找不到字体，使用PIL自带的默认字体
                font = ImageFont.load_default()
                logger.info("使用PIL默认字体")

        except Exception as e:
            logger.warning(f"加载字体失败，使用内置默认字体: {str(e)}")
            font = ImageFont.load_default()
        return font

    # 这个方法用来创建文本图像

    @staticmethod
    def create_text_image(text: str, size: Tuple[int, int],
                          font_size: int = 70,
                          color: Tuple[int, int, int] = (255, 255, 255),
                          bold: bool = False,
                          italic: bool = False,
                          underline: bool = False,
                          bg_color: str = "",
                          stroke_color: Tuple[int, int, int] = (0, 0, 0),
                          stroke_width: int = 0,
                          align: str = 'center') -> np.ndarray:
        # 创建透明背景图像
        img = Image.new('RGBA', size, color=0)
        draw = ImageDraw.Draw(img)
        # 加载字体
        font = VideoPreviewService.find_font(font_size)
        # 计算文本位置
        width, height = size
        # 处理文本对齐 这个是处理如果前端发送的文本中有换行符的情况 那么就切换为一行一行的去处理文本
        lines = text.split('\n')
        # 计算所有行的总高度，用于垂直居中
        total_text_height = 0
        line_heights = []
        max_line_width = 0

        # 循环便利每一行的文字
        for line in lines:
            bbox = draw.textbbox((0, 0), line, font=font)
            line_height = bbox[3] - bbox[1]
            line_width = bbox[2] - bbox[0]
            line_heights.append(line_height)
            total_text_height += line_height
            max_line_width = max(max_line_width, line_width)

        # 添加行间距
        if len(lines) > 1:
            line_spacing = max(10, font_size // 6)
            total_text_height += (len(lines) - 1) * line_spacing
        else:
            line_spacing = 0

        # 确保文本不超出画布
        margin = 40

        # 如果文本高度超过画布高度，重新计算字体大小
        if total_text_height > height - margin:
            # 重新计算字体大小
            scale_factor = (height - margin) / total_text_height
            font_size = int(font_size * scale_factor)

            # 重新计算高度
            total_text_height = 0
            line_heights = []
            max_line_width = 0

            for line in lines:
                bbox = draw.textbbox((0, 0), line, font=font)
                line_height = bbox[3] - bbox[1]
                line_width = bbox[2] - bbox[0]
                line_heights.append(line_height)
                total_text_height += line_height
                max_line_width = max(max_line_width, line_width)

            if len(lines) > 1:
                line_spacing = max(10, font_size // 6)
                total_text_height += (len(lines) - 1) * line_spacing

        # 当给负数时 就会跑出画布 如果给的负数足够大
        # 默认的基准线是视频的中央，数值越大越靠下
        y_position = 0
        # 绘制背景（如果指定）
        if bg_color:
            # 解析颜色
            if isinstance(bg_color, str) and bg_color.startswith('#'):
                r = int(bg_color[1:3], 16)
                g = int(bg_color[3:5], 16)
                b = int(bg_color[5:7], 16)
                bg_alpha = int(0.7 * 255)  # 70%透明度

                # 创建背景矩形 背景矩形就是文字的背景 （基于实际文本大小和位置）
                padding = 20
                rect_left = (width - max_line_width) // 2 - padding
                rect_top = 0
                rect_right = (width + max_line_width) // 2 + padding
                rect_bottom = total_text_height + padding

                # 确保背景矩形不超出画布
                rect_left = max(0, rect_left)
                rect_top = max(0, rect_top)
                rect_right = min(width, rect_right)
                rect_bottom = min(height, rect_bottom)

                # 绘制矩形
                draw.rectangle([(rect_left, rect_top), (rect_right, rect_bottom)],
                               fill=(r, g, b, bg_alpha))

        # 绘制每行文本
        for i, line in enumerate(lines):
            # 计算文本宽度，用于对齐
            bbox = draw.textbbox((0, 0), line, font=font)
            text_width = bbox[2] - bbox[0]
            line_height = line_heights[i]

            # 计算水平居中位置
            canvas_center_x = width // 2
            text_center_x = text_width // 2

            # 根据对齐方式确定x位置
            if align == 'center':
                x_position = canvas_center_x - text_center_x
            elif align == 'right':
                x_position = width - text_width - 20
            else:  # left
                x_position = 20

            # 确保文本不超出左右边界
            x_position = max(10, min(x_position, width - text_width - 10))

            # 如有描边，先绘制描边
            if stroke_color and stroke_width > 0:
                s_color = stroke_color + (255,)  # 添加alpha通道

                # 绘制多个偏移位置模拟描边
                for dx, dy in [(-1, -1), (-1, 0), (-1, 1), (0, -1),
                               (0, 1), (1, -1), (1, 0), (1, 1)]:
                    draw.text((x_position + dx * stroke_width,
                              y_position + dy * stroke_width),
                              line, font=font, fill=s_color)

            # 绘制文本 这里来绘制文本的展示  如果要调节位置 也在这里调节
            text_color = color + (int(255 * 0.95),)  # 添加alpha通道
            draw.text((x_position, y_position), line,
                      font=font, fill=text_color)

            # 如果有下划线
            if underline:
                underline_y = y_position + line_height + 2
                draw.line([(x_position, underline_y),
                          (x_position + text_width, underline_y)],
                          fill=text_color, width=max(1, font_size // 20))

            # 移动到下一行
            y_position += line_height + line_spacing

        # 转换为numpy数组，确保数据类型正确
        img_array = np.array(img)
        # 确保数据类型为uint8，这是PIL和moviepy兼容的格式
        if img_array.dtype != np.uint8:
            img_array = img_array.astype(np.uint8)

        return img_array

    @staticmethod
    def _load_font(font_size: int):
        """加载字体文件"""
        font_path = None
        try:
            if os.name == 'nt':  # Windows
                potential_fonts = [
                    os.path.join(os.environ['WINDIR'],
                                 'Fonts', 'simhei.ttf'),  # 黑体
                    os.path.join(os.environ['WINDIR'],
                                 'Fonts', 'simsun.ttc'),  # 宋体
                    os.path.join(os.environ['WINDIR'],
                                 'Fonts', 'msyh.ttc'),    # 微软雅黑
                    os.path.join(os.environ['WINDIR'],
                                 'Fonts', 'arial.ttf'),   # Arial
                ]

            for font_path in potential_fonts:
                if os.path.exists(font_path):
                    logger.info(f"使用系统字体: {font_path}")
                    break
            else:
                font_path = None

            if font_path:
                font = ImageFont.truetype(font_path, font_size)
                logger.info(f"成功加载字体: {font_path}")
            else:
                font = ImageFont.load_default()
                logger.info("使用PIL默认字体")

        except Exception as e:
            logger.warning(f"加载字体失败，使用内置默认字体: {str(e)}")
            font = ImageFont.load_default()
            font_path = None

        return font, font_path

    @staticmethod
    def _process_video_materials(video_conf: Dict[str, Any], width: int, height: int) -> Tuple[List, float]:
        """处理视频素材"""
        clips = []
        # 总时长
        total_duration = 0

        # 如果没有在请求体中配置video_conf 或者没有 video_materials 字段 那么就返回空
        if not video_conf or 'video_materials' not in video_conf:
            return clips, total_duration

        # 遍历所有视频素材 进行处理
        for video_material in video_conf['video_materials']:
            material: Dict[str, Any] = video_material.get('material', {})
            video_path = material.get('path')

            # 路径存在且为字符串
            if video_path and isinstance(video_path, str):

                # 处理路径中的反斜杠问题
                video_path = video_path.replace('\\\\', '\\')

                # 这里进行了一个判断，如果系统中存在这个视频文件
                if os.path.exists(video_path):
                    # clip 是一个视频剪辑对象 duration 是视频的总时长
                    clip, duration = VideoPreviewService._create_video_clip(
                        video_path, video_material, material, width, height)
                    clips.append(clip)

                    # 每次取最大的时长
                    total_duration = max(total_duration, duration)
                else:
                    placeholder_clips, duration = VideoPreviewService._create_placeholder_clips(
                        video_material, material, width, height)
                    clips.extend(placeholder_clips)
                    total_duration = max(total_duration, duration)

        return clips, total_duration

    @staticmethod
    def _create_video_clip(video_path: str, video_material: Dict[str, Any], material: Dict[str, Any], width: int, height: int) -> Tuple[object, float]:
        """创建视频剪辑"""

        # 获取开始时间
        start_time_us = video_material.get('start_time', 0)
        # 获取时长
        duration_us = video_material.get('duration', 0)
        start_time = start_time_us / TIME_BASE_CONVERSION
        duration = duration_us / TIME_BASE_CONVERSION

        # 加载视频
        video_clip = VideoFileClip(video_path).subclip(0, duration)

        # 应用裁剪设置
        crop_settings = material.get('crop_settings')
        if crop_settings:
            x1 = float(crop_settings.get('upper_left_x', 0.0)) * video_clip.w
            y1 = float(crop_settings.get('upper_left_y', 0.0)) * video_clip.h
            x2 = float(crop_settings.get('lower_right_x', 1.0)) * video_clip.w
            y2 = float(crop_settings.get('lower_right_y', 1.0)) * video_clip.h
            video_clip = video_clip.crop(x1=x1, y1=y1, x2=x2, y2=y2)

        # 调整大小到画布比例
        video_clip = video_clip.resize(width=width, height=height)
        video_clip = video_clip.set_start(start_time)

        return video_clip, start_time + duration

    @staticmethod
    def _create_placeholder_clips(video_material: Dict, material: Dict, width: int, height: int) -> Tuple[List, float]:
        """创建占位符剪辑"""
        logger.warning(f"⚠️ 视频路径无效或不存在: {material.get('path')}")

        material_name = material.get('name', '缺失视频')
        duration_us = video_material.get('duration', 8000000)
        duration = duration_us / TIME_BASE_CONVERSION
        start_time_us = video_material.get('start_time', 0)
        start_time = start_time_us / TIME_BASE_CONVERSION

        # 为不同轨道使用不同颜色
        track_name = video_material.get('track_name', '')
        color = (0, 0, 0) if '主背景' in track_name else (50, 50, 200)

        placeholder = ColorClip(size=(width, height),
                                color=color, duration=duration)
        placeholder = placeholder.set_start(start_time)

        # 创建占位符文本
        text_img = VideoPreviewService.create_text_image(
            text=f"未找到视频:\n{material_name}",
            size=(width, height),
            font_size=50,
            color=(255, 255, 255)
        )
        text_clip = ImageClip(text_img, duration=duration)
        text_clip = text_clip.set_start(start_time)

        return [placeholder, text_clip], start_time + duration

    @staticmethod
    def _create_text_clip(text_conf: Dict[str, Any], width: int, height: int) -> Tuple[object, float]:
        # 拿到文本信息
        text = text_conf.get('text', '')

        # 获取文本的开始时间和持续时间
        text_start_time_us = text_conf.get('start_time', 0)
        text_duration_us = text_conf.get('duration', 8000000)

        # 转换为标准的时间单位
        text_start_time = text_start_time_us / TIME_BASE_CONVERSION
        text_duration = text_duration_us / TIME_BASE_CONVERSION

        # 解析样式配置
        style = text_conf.get('style', {})
        font_size = int(style.get('size', 12) * 15)
        bold = style.get('bold', False)
        italic = style.get('italic', False)
        underline = style.get('underline', False)

        # 文本颜色
        font_color = style.get('color', [1, 1, 1])
        font_color_rgb = (
            int(font_color[0]*255), int(font_color[1]*255), int(font_color[2]*255))

        # 对齐方式
        align_value = style.get('align', 1)
        align_method = 'center' if align_value == 1 else (
            'right' if align_value == 2 else 'left')

        # 文本背景
        bg_color = '#000000'
        if text_conf.get('background'):
            bg_color = text_conf['background'].get('color', '#000000')

        # 描边
        stroke_color = (0, 0, 0)
        stroke_width = 0
        if text_conf.get('border'):
            border_color = text_conf['border'].get('color', [0, 0, 0])
            stroke_color = (
                int(border_color[0]*255), int(border_color[1]*255), int(border_color[2]*255))
            stroke_width = text_conf['border'].get('width', 1) // 5

        # 创建文本图像
        text_img = VideoPreviewService.create_text_image(
            text=text,
            size=(width, height),
            font_size=font_size,
            color=font_color_rgb,
            bold=bold,
            italic=italic,
            underline=underline,
            bg_color=bg_color,
            stroke_color=stroke_color,
            stroke_width=stroke_width,
            align=align_method
        )

        # 创建文本片段
        text_clip = ImageClip(text_img)
        text_clip = text_clip.set_duration(
            text_duration).set_start(text_start_time)

        return text_clip, text_start_time + text_duration

    # 配置动画的json 包含入场动画 出场动画 循环动画
    """
         "animations": {
            "intro": {
                "type": "旋转开幕",
                "duration": 1500000
            },
            "outro": {
                "type": "旋转",
                "duration": TIME_BASE_CONVERSION
            },
            "loop": {
                "type": "波浪"
            }
        }
    """
    @staticmethod
    def _apply_intro_animation(text_clip, animations: Dict[str, Any], width: int, height: int) -> ImageClip:
        """
        应用入场动画 - 支持所有Text_intro枚举中的动画类型

        Args:
            text_clip: 文本剪辑对象
            animations: 动画配置字典
            width: 画布宽度
            height: 画布高度

        Returns:
            ImageClip: 应用了入场动画的文本剪辑
        """
        if 'intro' not in animations:
            return text_clip

        intro_type: str = animations['intro'].get('type', '渐显')
        intro_duration_us: int = animations['intro'].get('duration', 1500000)
        intro_duration: float = intro_duration_us / TIME_BASE_CONVERSION

        logger.info(f"开始应用入场动画: {intro_type}，持续时间: {intro_duration}秒")

        # 尝试从Text_intro枚举中获取动画 - 使用name进行匹配
        animation_enum: Optional[Text_intro] = None
        try:
            for enum_item in Text_intro:
                if enum_item.name == intro_type:
                    animation_enum = enum_item
                    break

        except Exception as e:
            logger.warning(f"枚举匹配失败: {e}")

        if not animation_enum:
            logger.warning(f"未找到入场动画类型: {intro_type}，使用默认渐显")
            return text_clip.fadein(intro_duration)

        # 根据动画类型应用相应效果
        if animation_enum in [Text_intro.渐显]:
            return text_clip.fadein(intro_duration)

        elif animation_enum in [Text_intro.缩小, Text_intro.缩小_II]:
            return text_clip.resize(lambda t: min(1, 0.1 + 0.9 * (t / intro_duration)) if t < intro_duration else 1)

        elif animation_enum in [Text_intro.放大]:
            return text_clip.resize(lambda t: max(1, 2.0 - (t / intro_duration)) if t < intro_duration else 1)

        elif animation_enum in [Text_intro.旋入]:
            # 使用更安全的旋转方法
            try:
                return text_clip.rotate(lambda t: 360 * (1 - t / intro_duration) if t < intro_duration else 0)
            except Exception as e:
                logger.warning(f"旋转动画失败，使用渐显替代: {e}")
                return text_clip.fadein(intro_duration)

        elif animation_enum in [Text_intro.向上滑动]:
            return text_clip.set_position(lambda t: ('center', max(0, height * (1 - t / intro_duration))) if t < intro_duration else 'center')

        elif animation_enum in [Text_intro.向下滑动]:
            return text_clip.set_position(lambda t: ('center', min(0, -height * (1 - t / intro_duration))) if t < intro_duration else 'center')

        elif animation_enum in [Text_intro.向左滑动]:
            return text_clip.set_position(lambda t: (min(0, -width * (1 - t / intro_duration)), 'center') if t < intro_duration else 'center')

        elif animation_enum in [Text_intro.向右滑动]:
            return text_clip.set_position(lambda t: (max(0, width * (1 - t / intro_duration)), 'center') if t < intro_duration else 'center')

        elif animation_enum in [Text_intro.弹入]:
            def bounce_scale(t: float) -> float:
                if t >= intro_duration:
                    return 1.0
                progress = t / intro_duration
                return 0.1 + 0.9 * progress * (2 - progress)
            return text_clip.resize(bounce_scale)

        elif animation_enum in [Text_intro.弹簧]:
            def spring_scale(t: float) -> float:
                if t >= intro_duration:
                    return 1.0
                progress = t / intro_duration
                # 弹簧效果：先超调再回弹
                return 0.1 + 1.2 * progress - 0.3 * math.sin(progress * math.pi * 4) * (1 - progress)
            return text_clip.resize(spring_scale)

        elif animation_enum in [Text_intro.轻微放大]:
            return text_clip.resize(lambda t: max(1, 1.5 - 0.5 * (t / intro_duration)) if t < intro_duration else 1)

        elif animation_enum in [Text_intro.向上擦除, Text_intro.向下擦除, Text_intro.向左擦除, Text_intro.向右擦除]:
            # 擦除效果用透明度+位置模拟
            if animation_enum == Text_intro.向上擦除:
                def wipe_up_effect(t: float) -> Tuple[str, Union[str, float]]:
                    if t >= intro_duration:
                        return ('center', 'center')
                    progress = t / intro_duration
                    return ('center', height * (1 - progress))
                return text_clip.set_position(wipe_up_effect).fadein(intro_duration)
            elif animation_enum == Text_intro.向下擦除:
                def wipe_down_effect(t: float) -> Tuple[str, Union[str, float]]:
                    if t >= intro_duration:
                        return ('center', 'center')
                    progress = t / intro_duration
                    return ('center', -height * (1 - progress))
                return text_clip.set_position(wipe_down_effect).fadein(intro_duration)
            elif animation_enum == Text_intro.向左擦除:
                def wipe_left_effect(t: float) -> Tuple[Union[str, float], str]:
                    if t >= intro_duration:
                        return ('center', 'center')
                    progress = t / intro_duration
                    return (-width * (1 - progress), 'center')
                return text_clip.set_position(wipe_left_effect).fadein(intro_duration)
            else:  # 向右擦除
                def wipe_right_effect(t: float) -> Tuple[Union[str, float], str]:
                    if t >= intro_duration:
                        return ('center', 'center')
                    progress = t / intro_duration
                    return (width * (1 - progress), 'center')
                return text_clip.set_position(wipe_right_effect).fadein(intro_duration)

        elif animation_enum in [Text_intro.打字机_I, Text_intro.打字机_II, Text_intro.打字机_III, Text_intro.打字机IV]:
            # 打字机效果用透明度模拟
            def typewriter_opacity(t: float) -> float:
                if t >= intro_duration:
                    return 1.0
                progress = t / intro_duration
                # 模拟逐字显示效果
                return min(1.0, progress * 3)
            return text_clip.set_opacity(typewriter_opacity)

        elif animation_enum in [Text_intro.复古打字机]:
            # 复古打字机效果：结合位置抖动和透明度
            def vintage_typewriter(t: float) -> Tuple[Union[str, float], Union[str, float]]:
                if t >= intro_duration:
                    return ('center', 'center')
                progress = t / intro_duration
                shake_x = random.uniform(-2, 2) * (1 - progress)
                shake_y = random.uniform(-1, 1) * (1 - progress)
                return (width//2 + shake_x, height//2 + shake_y)

            return text_clip.set_position(vintage_typewriter).set_opacity(
                lambda t: min(1.0, (t / intro_duration) *
                              2) if t < intro_duration else 1.0
            )

        elif animation_enum in [Text_intro.模糊]:
            # 模糊效果用透明度模拟
            def blur_opacity(t: float) -> float:
                if t >= intro_duration:
                    return 1.0
                progress = t / intro_duration
                return 0.3 + 0.7 * progress
            return text_clip.set_opacity(blur_opacity)

        elif animation_enum in [Text_intro.晕开, Text_intro.水墨晕开]:
            # 晕开效果：缩放+透明度
            def bloom_effect(t: float) -> float:
                if t >= intro_duration:
                    return 1.0
                progress = t / intro_duration
                return 0.5 + 0.5 * progress

            def scale_func(t): return max(
                1, 2 - (t / intro_duration)) if t < intro_duration else 1
            return text_clip.resize(scale_func).set_opacity(bloom_effect)

        elif animation_enum in [Text_intro.波浪弹入]:
            def wave_position(t: float) -> Tuple[str, Union[str, float]]:
                if t >= intro_duration:
                    return ('center', 'center')
                progress = t / intro_duration
                wave_offset = 20 * \
                    math.sin(progress * math.pi * 6) * (1 - progress)
                return ('center', height//2 + wave_offset)
            return text_clip.set_position(wave_position)

        elif animation_enum in [Text_intro.扭曲模糊]:
            # 扭曲效果用随机位置偏移模拟
            def distort_position(t: float) -> Tuple[Union[str, float], Union[str, float]]:
                if t >= intro_duration:
                    return ('center', 'center')
                progress = t / intro_duration
                distort_intensity = 15 * (1 - progress)
                offset_x = random.uniform(-distort_intensity,
                                          distort_intensity)
                offset_y = random.uniform(-distort_intensity,
                                          distort_intensity)
                return (width//2 + offset_x, height//2 + offset_y)
            return text_clip.set_position(distort_position)

        elif animation_enum in [Text_intro.生长]:
            # 生长效果：从小到大
            def grow_scale(t: float) -> float:
                if t >= intro_duration:
                    return 1.0
                progress = t / intro_duration
                return progress
            return text_clip.resize(grow_scale)

        elif animation_enum in [Text_intro.收拢]:
            # 收拢效果：从分散到聚合
            def gather_position(t: float) -> Tuple[Union[str, float], Union[str, float]]:
                if t >= intro_duration:
                    return ('center', 'center')
                progress = t / intro_duration
                spread = 50 * (1 - progress)
                offset_x = random.uniform(-spread, spread)
                offset_y = random.uniform(-spread, spread)
                return (width//2 + offset_x, height//2 + offset_y)
            return text_clip.set_position(gather_position)

        elif animation_enum in [Text_intro.站起]:
            # 站起效果：垂直拉伸
            def stand_up_scale(t: float) -> Tuple[float, float]:
                if t >= intro_duration:
                    return (1.0, 1.0)
                progress = t / intro_duration
                return (1.0, progress)
            return text_clip.resize(stand_up_scale)

        elif animation_enum in [Text_intro.翻动]:
            # 翻动效果：水平翻转
            def flip_scale(t: float) -> Tuple[float, float]:
                if t >= intro_duration:
                    return (1.0, 1.0)
                progress = t / intro_duration
                scale_x = abs(math.cos(progress * math.pi))
                return (scale_x, 1.0)
            return text_clip.resize(flip_scale)

        elif animation_enum in [Text_intro.随机弹跳, Text_intro.随机飞入]:
            # 随机效果
            def random_motion(t: float) -> Tuple[Union[str, float], Union[str, float]]:
                if t >= intro_duration:
                    return ('center', 'center')
                progress = t / intro_duration
                random_intensity = 100 * (1 - progress)
                offset_x = random.uniform(-random_intensity, random_intensity)
                offset_y = random.uniform(-random_intensity, random_intensity)
                return (width//2 + offset_x, height//2 + offset_y)
            return text_clip.set_position(random_motion)

        # 付费动画效果（简化实现）
        elif hasattr(animation_enum.value, 'is_vip') and animation_enum.value.is_vip:
            logger.info(f"付费动画 {intro_type} 使用简化效果")

            if '故障' in intro_type or '乱码' in intro_type:
                def glitch_effect(t: float) -> Tuple[Union[str, float], Union[str, float]]:
                    if t >= intro_duration:
                        return ('center', 'center')
                    glitch_intensity = 20 * (1 - t / intro_duration)
                    offset_x = random.uniform(-glitch_intensity,
                                              glitch_intensity)
                    offset_y = random.uniform(-glitch_intensity,
                                              glitch_intensity)
                    return (width//2 + offset_x, height//2 + offset_y)
                return text_clip.set_position(glitch_effect)

            elif '模糊' in intro_type:
                def blur_opacity(t: float) -> float:
                    if t >= intro_duration:
                        return 1.0
                    progress = t / intro_duration
                    return 0.3 + 0.7 * progress
                return text_clip.set_opacity(blur_opacity)

            elif '旋转' in intro_type or '翻转' in intro_type:
                return text_clip.rotate(lambda t: 360 * (1 - t / intro_duration) if t < intro_duration else 0)

            elif '缩放' in intro_type or '放大' in intro_type:
                return text_clip.resize(lambda t: min(1, 0.1 + 0.9 * (t / intro_duration)) if t < intro_duration else 1)

            elif '滑' in intro_type or '飞' in intro_type:
                return text_clip.set_position(lambda t: ('center', max(0, height * (1 - t / intro_duration))) if t < intro_duration else 'center')

            else:
                # 默认渐显效果
                return text_clip.fadein(intro_duration)

        else:
            # 未匹配的免费动画，使用渐显
            logger.warning(f"动画类型 {intro_type} 暂未实现，使用默认渐显")
            return text_clip.fadein(intro_duration)

    @staticmethod
    def _apply_outro_animation(text_clip, animations: Dict[str, Any], text_duration: float, width: int, height: int) -> object:
        """
        应用出场动画 - 支持所有Text_outro枚举中的动画类型

        Args:
            text_clip: 文本剪辑对象
            animations: 动画配置字典
            text_duration: 文本总持续时间
            width: 画布宽度
            height: 画布高度

        Returns:
            object: 应用了出场动画的文本剪辑
        """
        if 'outro' not in animations:
            return text_clip

        outro_type: str = animations['outro'].get('type', '渐隐')
        outro_duration_us: int = animations['outro'].get(
            'duration', TIME_BASE_CONVERSION)
        outro_duration: float = outro_duration_us / TIME_BASE_CONVERSION
        outro_start: float = text_duration - outro_duration

        logger.info(f"开始应用出场动画: {outro_type}，持续时间: {outro_duration}秒")

        # 尝试从Text_outro枚举中获取动画 - 使用name进行匹配
        animation_enum: Optional[Text_outro] = None
        try:
            for enum_item in Text_outro:
                if enum_item.name == outro_type:
                    animation_enum = enum_item
                    break

        except Exception as e:
            logger.warning(f"枚举匹配失败: {e}")

        if not animation_enum:
            logger.warning(f"未找到出场动画类型: {outro_type}，使用默认渐隐")
            return text_clip.fadeout(outro_duration)

        try:
            # 根据动画类型应用相应效果
            if animation_enum in [Text_outro.渐隐]:
                return text_clip.fadeout(outro_duration)

            elif animation_enum in [Text_outro.缩小]:
                def shrink_scale(t: float) -> float:
                    if t < outro_start:
                        return 1.0
                    progress = (t - outro_start) / outro_duration
                    return max(0.1, 1 - 0.9 * progress)
                return text_clip.resize(shrink_scale)

            elif animation_enum in [Text_outro.放大]:
                def expand_scale(t: float) -> float:
                    if t < outro_start:
                        return 1.0
                    progress = (t - outro_start) / outro_duration
                    return 1 + 2 * progress
                return text_clip.resize(expand_scale).fadeout(outro_duration)

            elif animation_enum in [Text_outro.旋出]:
                # 使用更安全的旋转方法
                def rotate_out(t: float) -> float:
                    if t < outro_start:
                        return 0.0
                    progress = (t - outro_start) / outro_duration
                    return 360 * progress
                try:
                    return text_clip.rotate(rotate_out).fadeout(outro_duration)
                except Exception as e:
                    logger.warning(f"旋转出场动画失败，使用渐隐替代: {e}")
                    return text_clip.fadeout(outro_duration)

            elif animation_enum in [Text_outro.向上滑动]:
                def slide_up(t: float) -> Tuple[str, Union[str, float]]:
                    if t < outro_start:
                        return ('center', 'center')
                    progress = (t - outro_start) / outro_duration
                    return ('center', -height * progress)
                return text_clip.set_position(slide_up)

            elif animation_enum in [Text_outro.向下滑动]:
                def slide_down(t: float) -> Tuple[str, Union[str, float]]:
                    if t < outro_start:
                        return ('center', 'center')
                    progress = (t - outro_start) / outro_duration
                    return ('center', height * progress)
                return text_clip.set_position(slide_down)

            elif animation_enum in [Text_outro.向左滑动]:
                def slide_left(t: float) -> Tuple[Union[str, float], str]:
                    if t < outro_start:
                        return ('center', 'center')
                    progress = (t - outro_start) / outro_duration
                    return (-width * progress, 'center')
                return text_clip.set_position(slide_left)

            elif animation_enum in [Text_outro.向右滑动]:
                def slide_right(t: float) -> Tuple[Union[str, float], str]:
                    if t < outro_start:
                        return ('center', 'center')
                    progress = (t - outro_start) / outro_duration
                    return (width * progress, 'center')
                return text_clip.set_position(slide_right)

            elif animation_enum in [Text_outro.向上擦除]:
                def wipe_up(t: float) -> Tuple[str, Union[str, float]]:
                    if t < outro_start:
                        return ('center', 'center')
                    progress = (t - outro_start) / outro_duration
                    return ('center', -height * progress)
                return text_clip.set_position(wipe_up).fadeout(outro_duration)

            elif animation_enum in [Text_outro.向下擦除]:
                def wipe_down(t: float) -> Tuple[str, Union[str, float]]:
                    if t < outro_start:
                        return ('center', 'center')
                    progress = (t - outro_start) / outro_duration
                    return ('center', height * progress)
                return text_clip.set_position(wipe_down).fadeout(outro_duration)

            elif animation_enum in [Text_outro.向左擦除]:
                def wipe_left(t: float) -> Tuple[Union[str, float], str]:
                    if t < outro_start:
                        return ('center', 'center')
                    progress = (t - outro_start) / outro_duration
                    return (-width * progress, 'center')
                return text_clip.set_position(wipe_left).fadeout(outro_duration)

            elif animation_enum in [Text_outro.向右擦除]:
                def wipe_right(t: float) -> Tuple[Union[str, float], str]:
                    if t < outro_start:
                        return ('center', 'center')
                    progress = (t - outro_start) / outro_duration
                    return (width * progress, 'center')
                return text_clip.set_position(wipe_right).fadeout(outro_duration)

            elif animation_enum in [Text_outro.右上弹出]:
                def bounce_out_top_right(t: float) -> Tuple[Union[str, float], Union[str, float]]:
                    if t < outro_start:
                        return ('center', 'center')
                    progress = (t - outro_start) / outro_duration
                    # 弹跳效果：先减速再加速
                    bounce_progress = progress * (2 - progress)
                    return (width//2 + width * bounce_progress, height//2 - height * bounce_progress)
                return text_clip.set_position(bounce_out_top_right)

            elif animation_enum in [Text_outro.左上弹出]:
                def bounce_out_top_left(t: float) -> Tuple[Union[str, float], Union[str, float]]:
                    if t < outro_start:
                        return ('center', 'center')
                    progress = (t - outro_start) / outro_duration
                    bounce_progress = progress * (2 - progress)
                    return (width//2 - width * bounce_progress, height//2 - height * bounce_progress)
                return text_clip.set_position(bounce_out_top_left)

            elif animation_enum in [Text_outro.弹出]:
                def bounce_out(t: float) -> float:
                    if t < outro_start:
                        return 1.0
                    progress = (t - outro_start) / outro_duration
                    # 弹出效果：先快速放大，然后淡出
                    return 1 + 3 * progress * progress
                return text_clip.resize(bounce_out).fadeout(outro_duration)

            elif animation_enum in [Text_outro.弹弓]:
                def slingshot_out(t: float) -> Tuple[Union[str, float], Union[str, float]]:
                    if t < outro_start:
                        return ('center', 'center')
                    progress = (t - outro_start) / outro_duration
                    # 弹弓效果：快速向上飞出
                    acceleration = progress * progress
                    return ('center', height//2 - height * 2 * acceleration)
                return text_clip.set_position(slingshot_out)

            elif animation_enum in [Text_outro.弹性伸缩, Text_outro.弹性伸缩_II]:
                def elastic_scale(t: float) -> Tuple[float, float]:
                    if t < outro_start:
                        return (1.0, 1.0)
                    progress = (t - outro_start) / outro_duration
                    # 弹性效果：震荡缩小
                    scale = 1 - progress + 0.2 * \
                        math.sin(progress * math.pi * 6) * (1 - progress)
                    return (max(0.1, scale), max(0.1, scale))
                return text_clip.resize(elastic_scale)

            elif animation_enum in [Text_outro.弹簧]:
                def spring_out(t: float) -> float:
                    if t < outro_start:
                        return 1.0
                    progress = (t - outro_start) / outro_duration
                    # 弹簧效果：震荡消失
                    spring_scale = 1 - progress + 0.3 * \
                        math.sin(progress * math.pi * 8) * (1 - progress)
                    return max(0.1, spring_scale)
                return text_clip.resize(spring_out)

            elif animation_enum in [Text_outro.打字机_I, Text_outro.打字机_II, Text_outro.打字机_III, Text_outro.打字机IV]:
                # 打字机效果：逐字消失
                def typewriter_out(t: float) -> float:
                    if t < outro_start:
                        return 1.0
                    progress = (t - outro_start) / outro_duration
                    return max(0.0, 1 - progress * 3)
                return text_clip.set_opacity(typewriter_out)

            elif animation_enum in [Text_outro.扭曲模糊]:
                def distort_out(t: float) -> Tuple[Union[str, float], Union[str, float]]:
                    if t < outro_start:
                        return ('center', 'center')
                    progress = (t - outro_start) / outro_duration
                    distort_intensity = 20 * progress
                    offset_x = random.uniform(-distort_intensity,
                                              distort_intensity)
                    offset_y = random.uniform(-distort_intensity,
                                              distort_intensity)
                    return (width//2 + offset_x, height//2 + offset_y)
                return text_clip.set_position(distort_out).fadeout(outro_duration)

            elif animation_enum in [Text_outro.模糊]:
                def blur_out(t: float) -> float:
                    if t < outro_start:
                        return 1.0
                    progress = (t - outro_start) / outro_duration
                    return max(0.2, 1 - 0.8 * progress)
                return text_clip.set_opacity(blur_out)

            elif animation_enum in [Text_outro.晕开, Text_outro.水墨晕开]:
                def bloom_out(t: float) -> float:
                    if t < outro_start:
                        return 1.0
                    progress = (t - outro_start) / outro_duration
                    return max(0.2, 1 - 0.8 * progress)

                def scale_out(t: float) -> float:
                    if t < outro_start:
                        return 1.0
                    progress = (t - outro_start) / outro_duration
                    return 1 + progress

                return text_clip.resize(scale_out).set_opacity(bloom_out)

            elif animation_enum in [Text_outro.波浪弹出]:
                def wave_out(t: float) -> Tuple[str, Union[str, float]]:
                    if t < outro_start:
                        return ('center', 'center')
                    progress = (t - outro_start) / outro_duration
                    wave_offset = 30 * \
                        math.sin(progress * math.pi * 4) * progress
                    return ('center', height//2 + wave_offset)
                return text_clip.set_position(wave_out).fadeout(outro_duration)

            elif animation_enum in [Text_outro.溶解]:
                def dissolve_out(t: float) -> float:
                    if t < outro_start:
                        return 1.0
                    progress = (t - outro_start) / outro_duration
                    # 溶解效果：随机透明度
                    base_opacity = 1 - progress
                    noise = random.uniform(-0.2, 0.2) * progress
                    return max(0.0, base_opacity + noise)
                return text_clip.set_opacity(dissolve_out)

            elif animation_enum in [Text_outro.生长]:
                def shrink_out(t: float) -> float:
                    if t < outro_start:
                        return 1.0
                    progress = (t - outro_start) / outro_duration
                    return max(0.0, 1 - progress)
                return text_clip.resize(shrink_out)

            elif animation_enum in [Text_outro.轻微放大]:
                def slight_expand_out(t: float) -> float:
                    if t < outro_start:
                        return 1.0
                    progress = (t - outro_start) / outro_duration
                    return 1 + 0.3 * progress
                return text_clip.resize(slight_expand_out).fadeout(outro_duration)

            elif animation_enum in [Text_outro.闪动]:
                def blink_out(t: float) -> float:
                    if t < outro_start:
                        return 1.0
                    progress = (t - outro_start) / outro_duration
                    # 闪烁效果：快速闪烁然后消失
                    if progress < 0.8:
                        return 0.5 + 0.5 * math.sin(progress * math.pi * 20)
                    else:
                        return max(0.0, 1 - (progress - 0.8) * 5)
                return text_clip.set_opacity(blink_out)

            elif animation_enum in [Text_outro.随机弹跳, Text_outro.随机飞出]:
                def random_out(t: float) -> Tuple[Union[str, float], Union[str, float]]:
                    if t < outro_start:
                        return ('center', 'center')
                    progress = (t - outro_start) / outro_duration
                    random_intensity = 150 * progress
                    offset_x = random.uniform(-random_intensity,
                                              random_intensity)
                    offset_y = random.uniform(-random_intensity,
                                              random_intensity)
                    return (width//2 + offset_x, height//2 + offset_y)
                return text_clip.set_position(random_out)

            elif animation_enum in [Text_outro.翻动]:
                def flip_out(t: float) -> Tuple[float, float]:
                    if t < outro_start:
                        return (1.0, 1.0)
                    progress = (t - outro_start) / outro_duration
                    scale_x = abs(math.cos(progress * math.pi * 2))
                    return (scale_x, 1.0)
                return text_clip.resize(flip_out).fadeout(outro_duration)

            elif animation_enum in [Text_outro.躺下]:
                def lie_down(t: float) -> Tuple[float, float]:
                    if t < outro_start:
                        return (1.0, 1.0)
                    progress = (t - outro_start) / outro_duration
                    scale_y = max(0.1, 1 - progress * 0.8)
                    return (1.0, scale_y)
                return text_clip.resize(lie_down)

            elif animation_enum in [Text_outro.闭幕]:
                def close_curtain(t: float) -> Tuple[float, float]:
                    if t < outro_start:
                        return (1.0, 1.0)
                    progress = (t - outro_start) / outro_duration
                    scale_x = max(0.1, 1 - progress)
                    return (scale_x, 1.0)
                return text_clip.resize(close_curtain)

            # 付费动画效果（简化实现）
            elif hasattr(animation_enum.value, 'is_vip') and animation_enum.value.is_vip:
                logger.info(f"付费出场动画 {outro_type} 使用简化效果")

                if '故障' in outro_type:
                    def glitch_out(t: float) -> Tuple[Union[str, float], Union[str, float]]:
                        if t < outro_start:
                            return ('center', 'center')
                        progress = (t - outro_start) / outro_duration
                        glitch_intensity = 30 * progress
                        offset_x = random.uniform(-glitch_intensity,
                                                  glitch_intensity)
                        offset_y = random.uniform(-glitch_intensity,
                                                  glitch_intensity)
                        return (width//2 + offset_x, height//2 + offset_y)
                    return text_clip.set_position(glitch_out).fadeout(outro_duration)

                elif '模糊' in outro_type:
                    def blur_out(t: float) -> float:
                        if t < outro_start:
                            return 1.0
                        progress = (t - outro_start) / outro_duration
                        return max(0.2, 1 - 0.8 * progress)
                    return text_clip.set_opacity(blur_out)

                elif '旋转' in outro_type or '翻转' in outro_type:
                    def rotate_out(t: float) -> float:
                        if t < outro_start:
                            return 0.0
                        progress = (t - outro_start) / outro_duration
                        return 360 * progress
                    try:
                        return text_clip.rotate(rotate_out).fadeout(outro_duration)
                    except Exception as e:
                        logger.warning(f"付费旋转出场动画失败，使用渐隐替代: {e}")
                        return text_clip.fadeout(outro_duration)

                elif '缩放' in outro_type or '放大' in outro_type:
                    def scale_out(t: float) -> float:
                        if t < outro_start:
                            return 1.0
                        progress = (t - outro_start) / outro_duration
                        return 1 + 2 * progress
                    return text_clip.resize(scale_out).fadeout(outro_duration)

                elif '滑' in outro_type or '飞' in outro_type:
                    def slide_out(t: float) -> Tuple[str, Union[str, float]]:
                        if t < outro_start:
                            return ('center', 'center')
                        progress = (t - outro_start) / outro_duration
                        return ('center', -height * progress)
                    return text_clip.set_position(slide_out)

                elif '炸开' in outro_type:
                    def explode_out(t: float) -> float:
                        if t < outro_start:
                            return 1.0
                        progress = (t - outro_start) / outro_duration
                        return 1 + 5 * progress * progress
                    return text_clip.resize(explode_out).fadeout(outro_duration)

                else:
                    # 默认渐隐效果
                    return text_clip.fadeout(outro_duration)

            else:
                # 未匹配的免费动画，使用渐隐
                logger.warning(f"动画类型 {outro_type} 暂未实现，使用默认渐隐")
                return text_clip.fadeout(outro_duration)

        except Exception as e:
            logger.error(f"出场动画 {outro_type} 执行失败: {e}，使用渐隐替代")
            return text_clip.fadeout(outro_duration)

    @staticmethod
    def _apply_loop_animation(text_clip, animations: Dict, width: int, height: int) -> object:
        """
        应用循环动画 - 支持所有Text_loop_anim枚举中的动画类型

        Args:
            text_clip: 文本剪辑对象
            animations: 动画配置字典
            width: 画布宽度
            height: 画布高度

        Returns:
            object: 应用了循环动画的文本剪辑
        """
        if 'loop' not in animations:
            return text_clip

        loop_type: str = animations['loop'].get('type', '跳动')
        logger.info(f"开始应用循环动画: {loop_type}")

        # 尝试从Text_loop_anim枚举中获取动画 - 使用name进行匹配
        animation_enum: Optional[Text_loop_anim] = None
        try:
            for enum_item in Text_loop_anim:
                if enum_item.name == loop_type:
                    animation_enum = enum_item
                    break

        except Exception as e:
            logger.warning(f"枚举匹配失败: {e}")

        if not animation_enum:
            logger.warning(f"未找到循环动画类型: {loop_type}，使用默认跳动")
            # 使用默认跳动效果

            def default_bounce_y(t):
                bounce_period = 0.8
                bounce_height = 20
                y_offset = abs(math.sin(t * 2 * math.pi /
                               bounce_period)) * bounce_height
                return ('center', height//2 - y_offset)
            return text_clip.set_position(default_bounce_y)

        try:
            # 根据动画类型应用相应效果
            if animation_enum in [Text_loop_anim.跳动]:
                def bounce_y(t):
                    bounce_period = 0.8
                    bounce_height = 20
                    y_offset = abs(math.sin(t * 2 * math.pi /
                                   bounce_period)) * bounce_height
                    return ('center', height//2 - y_offset)
                return text_clip.set_position(bounce_y)

            elif animation_enum in [Text_loop_anim.旋转]:
                def continuous_rotate(t):
                    rotation_period = 4
                    return (t % rotation_period) / rotation_period * 360
                try:
                    return text_clip.rotate(continuous_rotate)
                except Exception as e:
                    logger.warning(f"旋转循环动画失败，跳过: {e}")
                    return text_clip

            elif animation_enum in [Text_loop_anim.颤抖]:
                def shake_loop(t):
                    shake_intensity = 5
                    offset_x = random.uniform(-shake_intensity,
                                              shake_intensity)
                    offset_y = random.uniform(-shake_intensity,
                                              shake_intensity)
                    return (width//2 + offset_x, height//2 + offset_y)
                return text_clip.set_position(shake_loop)

            elif animation_enum in [Text_loop_anim.闪烁]:
                def blink_opacity(t):
                    blink_period = 1.5
                    return 0.3 + 0.7 * (0.5 + 0.5 * math.sin(t * 2 * math.pi / blink_period))
                return text_clip.set_opacity(blink_opacity)

            elif animation_enum in [Text_loop_anim.摇摆]:
                def sway_x(t):
                    sway_period = 2
                    sway_range = 30
                    x_offset = math.sin(
                        t * 2 * math.pi / sway_period) * sway_range
                    return (width//2 + x_offset, 'center')
                return text_clip.set_position(sway_x)

            elif animation_enum in [Text_loop_anim.波浪]:
                def wave_y(t):
                    wave_period = 3
                    wave_amplitude = 15
                    y_offset = math.sin(
                        t * 2 * math.pi / wave_period) * wave_amplitude
                    return ('center', height//2 + y_offset)
                return text_clip.set_position(wave_y)

            elif animation_enum in [Text_loop_anim.环绕]:
                def circular_motion(t):
                    circle_period = 5
                    radius = 40
                    angle = (t % circle_period) / circle_period * 2 * math.pi
                    x_offset = math.cos(angle) * radius
                    y_offset = math.sin(angle) * radius
                    return (width//2 + x_offset, height//2 + y_offset)
                return text_clip.set_position(circular_motion)

            elif animation_enum in [Text_loop_anim.钟摆]:
                def pendulum_rotation(t):
                    pendulum_period = 3
                    max_angle = 15
                    return math.sin(t * 2 * math.pi / pendulum_period) * max_angle
                try:
                    return text_clip.rotate(pendulum_rotation)
                except Exception as e:
                    logger.warning(f"钟摆循环动画失败，跳过: {e}")
                    return text_clip

            elif animation_enum in [Text_loop_anim.翻转]:
                def flip_scale(t):
                    flip_period = 2
                    progress = (t % flip_period) / flip_period
                    scale_x = math.cos(progress * 2 * math.pi)
                    return (abs(scale_x), 1)
                return text_clip.resize(flip_scale)

            elif animation_enum in [Text_loop_anim.雨刷]:
                def wiper_rotation(t):
                    wiper_period = 2.5
                    max_angle = 45
                    progress = (t % wiper_period) / wiper_period
                    if progress < 0.5:
                        return max_angle * (progress * 2)
                    else:
                        return max_angle * (2 - progress * 2)
                try:
                    return text_clip.rotate(wiper_rotation)
                except Exception as e:
                    logger.warning(f"雨刷循环动画失败，跳过: {e}")
                    return text_clip

            elif animation_enum in [Text_loop_anim.上下浮动]:
                def float_vertical(t):
                    float_period = 2.5
                    float_amplitude = 25
                    y_offset = math.sin(
                        t * 2 * math.pi / float_period) * float_amplitude
                    return ('center', height//2 + y_offset)
                return text_clip.set_position(float_vertical)

            elif animation_enum in [Text_loop_anim.左右浮动]:
                def float_horizontal(t):
                    float_period = 3
                    float_amplitude = 35
                    x_offset = math.sin(
                        t * 2 * math.pi / float_period) * float_amplitude
                    return (width//2 + x_offset, 'center')
                return text_clip.set_position(float_horizontal)

            elif animation_enum in [Text_loop_anim.缩放呼吸]:
                def breathing_scale(t):
                    breath_period = 2
                    scale_range = 0.2
                    scale = 1 + scale_range * \
                        math.sin(t * 2 * math.pi / breath_period)
                    return scale
                return text_clip.resize(breathing_scale)

            # 付费循环动画效果（简化实现）
            elif hasattr(animation_enum.value, 'is_vip') and animation_enum.value.is_vip:
                logger.info(f"付费循环动画 {loop_type} 使用简化效果")

                if animation_enum in [Text_loop_anim.霓虹闪烁]:
                    def neon_blink(t):
                        blink_period = 0.8
                        # 霓虹效果：快速闪烁
                        base_opacity = 0.7
                        flash_intensity = 0.3
                        return base_opacity + flash_intensity * abs(math.sin(t * 2 * math.pi / blink_period * 3))
                    return text_clip.set_opacity(neon_blink)

                elif animation_enum in [Text_loop_anim.故障抖动]:
                    def glitch_shake(t):
                        glitch_intensity = 8
                        # 随机故障抖动
                        if random.random() < 0.1:  # 10%概率发生故障
                            offset_x = random.uniform(-glitch_intensity,
                                                      glitch_intensity)
                            offset_y = random.uniform(-glitch_intensity,
                                                      glitch_intensity)
                            return (width//2 + offset_x, height//2 + offset_y)
                        return ('center', 'center')
                    return text_clip.set_position(glitch_shake)

                elif animation_enum in [Text_loop_anim.发光脉冲]:
                    def glow_pulse(t):
                        pulse_period = 1.5
                        # 发光脉冲：透明度+缩放
                        pulse = math.sin(t * 2 * math.pi / pulse_period)
                        opacity = 0.6 + 0.4 * abs(pulse)
                        return opacity

                    def pulse_scale(t):
                        pulse_period = 1.5
                        pulse = math.sin(t * 2 * math.pi / pulse_period)
                        scale = 1 + 0.1 * abs(pulse)
                        return scale
                    return text_clip.set_opacity(glow_pulse).resize(pulse_scale)

                elif animation_enum in [Text_loop_anim.彩虹渐变]:
                    # 彩虹渐变用透明度变化模拟
                    def rainbow_opacity(t):
                        rainbow_period = 4
                        return 0.7 + 0.3 * math.sin(t * 2 * math.pi / rainbow_period)
                    return text_clip.set_opacity(rainbow_opacity)

                elif animation_enum in [Text_loop_anim.电光闪烁]:
                    def electric_flash(t):
                        # 电光效果：随机闪烁
                        if random.random() < 0.05:  # 5%概率闪烁
                            return 1.0
                        return 0.8 + 0.2 * random.random()
                    return text_clip.set_opacity(electric_flash)

                elif animation_enum in [Text_loop_anim.心跳节奏]:
                    def heartbeat_scale(t):
                        beat_period = 1.2
                        # 心跳效果：双重脉冲
                        beat = abs(math.sin(t * 2 * math.pi / beat_period))
                        double_beat = abs(
                            math.sin(t * 4 * math.pi / beat_period)) * 0.5
                        scale = 1 + 0.15 * (beat + double_beat)
                        return scale
                    return text_clip.resize(heartbeat_scale)

                elif animation_enum in [Text_loop_anim.音波律动]:
                    def audio_wave(t):
                        wave_period = 0.6
                        # 音波效果：快速波动
                        wave1 = math.sin(t * 2 * math.pi / wave_period)
                        wave2 = math.sin(t * 2 * math.pi /
                                         wave_period * 1.5) * 0.5
                        scale = 1 + 0.1 * (wave1 + wave2)
                        return max(0.8, scale)
                    return text_clip.resize(audio_wave)

                elif animation_enum in [Text_loop_anim.火焰摇曳]:
                    def flame_sway(t):
                        sway_period = 2
                        # 火焰摇曳：不规则摆动
                        base_sway = math.sin(
                            t * 2 * math.pi / sway_period) * 20
                        random_sway = random.uniform(-5, 5)
                        x_offset = base_sway + random_sway
                        y_offset = random.uniform(-3, 3)
                        return (width//2 + x_offset, height//2 + y_offset)
                    return text_clip.set_position(flame_sway)

                elif animation_enum in [Text_loop_anim.水波涟漪]:
                    def water_ripple(t):
                        ripple_period = 3
                        # 水波涟漪：多层波动
                        ripple1 = math.sin(
                            t * 2 * math.pi / ripple_period) * 10
                        ripple2 = math.sin(
                            t * 2 * math.pi / ripple_period * 1.3) * 5
                        y_offset = ripple1 + ripple2
                        return ('center', height//2 + y_offset)
                    return text_clip.set_position(water_ripple)

                elif animation_enum in [Text_loop_anim.星光闪烁]:
                    def starlight_twinkle(t):
                        twinkle_period = 1
                        # 星光闪烁：随机明暗变化
                        base_opacity = 0.8
                        twinkle = 0.2 * \
                            math.sin(t * 2 * math.pi / twinkle_period)
                        random_twinkle = 0.1 * random.uniform(-1, 1)
                        return base_opacity + twinkle + random_twinkle
                    return text_clip.set_opacity(starlight_twinkle)

                elif animation_enum in [Text_loop_anim.魔法光环]:
                    def magic_halo(t):
                        halo_period = 4
                        # 魔法光环：圆形运动+缩放
                        radius = 30
                        angle = (t % halo_period) / halo_period * 2 * math.pi
                        x_offset = math.cos(angle) * radius * 0.3  # 椭圆运动
                        y_offset = math.sin(angle) * radius
                        return (width//2 + x_offset, height//2 + y_offset)

                    def halo_scale(t):
                        scale_period = 2
                        scale = 1 + 0.1 * \
                            math.sin(t * 2 * math.pi / scale_period)
                        return scale
                    return text_clip.set_position(magic_halo).resize(halo_scale)

                elif animation_enum in [Text_loop_anim.震荡波纹]:
                    def vibration_wave(t):
                        vibrate_period = 0.1
                        # 震荡波纹：高频小幅震动
                        intensity = 3
                        x_vibrate = math.sin(
                            t * 2 * math.pi / vibrate_period) * intensity
                        y_vibrate = math.cos(
                            t * 2 * math.pi / vibrate_period * 1.2) * intensity
                        return (width//2 + x_vibrate, height//2 + y_vibrate)
                    return text_clip.set_position(vibration_wave)

                elif animation_enum in [Text_loop_anim.螺旋旋转]:
                    spiral_period = 6

                    def spiral_rotation(t):
                        # 螺旋旋转：旋转+放射运动
                        radius = 20 * \
                            math.sin(t * 2 * math.pi / spiral_period * 0.5)
                        angle = (t % spiral_period) / \
                            spiral_period * 4 * math.pi
                        x_offset = math.cos(angle) * radius
                        y_offset = math.sin(angle) * radius
                        return (width//2 + x_offset, height//2 + y_offset)
                    try:
                        def rotation(t): return (
                            t % spiral_period) / spiral_period * 360
                        return text_clip.set_position(spiral_rotation).rotate(rotation)
                    except Exception as e:
                        logger.warning(f"螺旋旋转动画失败，使用简化版本: {e}")
                        return text_clip.set_position(spiral_rotation)

                elif animation_enum in [Text_loop_anim.弹性伸缩]:
                    def elastic_stretch(t):
                        stretch_period = 1.8
                        # 弹性伸缩：非线性缩放
                        stretch_phase = t * 2 * math.pi / stretch_period
                        scale_x = 1 + 0.15 * math.sin(stretch_phase)
                        scale_y = 1 + 0.1 * math.sin(stretch_phase * 1.3)
                        return (scale_x, scale_y)
                    return text_clip.resize(elastic_stretch)

                elif animation_enum in [Text_loop_anim.漂浮摆动]:
                    def floating_sway(t):
                        float_period = 4
                        sway_period = 2.5
                        # 漂浮摆动：垂直漂浮+水平摆动
                        float_y = math.sin(t * 2 * math.pi / float_period) * 15
                        sway_x = math.sin(t * 2 * math.pi / sway_period) * 25
                        return (width//2 + sway_x, height//2 + float_y)
                    return text_clip.set_position(floating_sway)

                elif animation_enum in [Text_loop_anim.色彩变幻]:
                    # 色彩变幻用透明度和位置微调模拟
                    def color_shift(t):
                        shift_period = 3
                        opacity = 0.8 + 0.2 * \
                            math.sin(t * 2 * math.pi / shift_period)
                        return opacity

                    def color_position(t):
                        shift_x = math.sin(t * 2 * math.pi / 5) * 2
                        shift_y = math.cos(t * 2 * math.pi / 7) * 2
                        return (width//2 + shift_x, height//2 + shift_y)
                    return text_clip.set_opacity(color_shift).set_position(color_position)

                elif animation_enum in [Text_loop_anim.立体翻转]:
                    def cube_flip(t):
                        flip_period = 3
                        # 立体翻转：3D翻转效果模拟
                        progress = (t % flip_period) / flip_period
                        if progress < 0.25:
                            scale_x = math.cos(progress * 4 * math.pi)
                            return (abs(scale_x), 1)
                        elif progress < 0.75:
                            scale_y = math.cos((progress - 0.25) * 4 * math.pi)
                            return (1, abs(scale_y))
                        else:
                            scale_x = math.cos((progress - 0.75) * 4 * math.pi)
                            return (abs(scale_x), 1)
                    return text_clip.resize(cube_flip)

                elif animation_enum in [Text_loop_anim.光束扫描]:
                    def beam_scan(t):
                        scan_period = 2
                        # 光束扫描：透明度扫描效果
                        scan_progress = (t % scan_period) / scan_period
                        opacity = 0.4 + 0.6 * \
                            abs(math.sin(scan_progress * math.pi))
                        return opacity
                    return text_clip.set_opacity(beam_scan)

                elif animation_enum in [Text_loop_anim.能量充电]:
                    def energy_charge(t):
                        charge_period = 2.5
                        # 能量充电：脉冲式放大
                        charge_phase = t * 2 * math.pi / charge_period
                        if math.sin(charge_phase) > 0:
                            intensity = math.sin(charge_phase)
                            scale = 1 + 0.2 * intensity
                            opacity = 0.7 + 0.3 * intensity
                            return scale, opacity
                        return 1, 0.7

                    def charge_scale(t):
                        scale, _ = energy_charge(t)
                        return scale

                    def charge_opacity(t):
                        _, opacity = energy_charge(t)
                        return opacity

                    return text_clip.resize(charge_scale).set_opacity(charge_opacity)

                elif animation_enum in [Text_loop_anim.数字雨]:
                    def digital_rain(t):
                        rain_period = 0.2
                        # 数字雨：快速位置变化
                        if random.random() < 0.1:  # 10%概率移动
                            offset_x = random.uniform(-5, 5)
                            offset_y = random.uniform(-3, 3)
                            return (width//2 + offset_x, height//2 + offset_y)
                        return ('center', 'center')

                    def rain_opacity(t):
                        return 0.7 + 0.3 * random.random()
                    return text_clip.set_position(digital_rain).set_opacity(rain_opacity)

                else:
                    # 其他付费动画使用默认效果
                    logger.info(f"付费动画 {loop_type} 使用默认跳动效果")

                    def default_bounce(t):
                        bounce_period = 0.8
                        bounce_height = 20
                        y_offset = abs(
                            math.sin(t * 2 * math.pi / bounce_period)) * bounce_height
                        return ('center', height//2 - y_offset)
                    return text_clip.set_position(default_bounce)

            else:
                # 未匹配的免费动画，使用跳动
                logger.warning(f"动画类型 {loop_type} 暂未实现，使用默认跳动")

                def default_bounce_y(t):
                    bounce_period = 0.8
                    bounce_height = 20
                    y_offset = abs(math.sin(t * 2 * math.pi /
                                   bounce_period)) * bounce_height
                    return ('center', height//2 - y_offset)
                return text_clip.set_position(default_bounce_y)

        except Exception as e:
            logger.error(f"循环动画 {loop_type} 执行失败: {e}")
            # 失败时返回原始剪辑
            return text_clip

    @staticmethod
    def _process_text_animations(text_clip: object, text_conf: Dict[str, Any], text_duration: float, width: int, height: int) -> object:
        """处理文本动画效果"""
        animations = text_conf.get('animations', {})

        # 应用入场动画
        text_clip = VideoPreviewService._apply_intro_animation(
            text_clip, animations, width, height)

        # 应用出场动画
        text_clip = VideoPreviewService._apply_outro_animation(
            text_clip, animations, text_duration, width, height)

        # 应用循环动画
        text_clip = VideoPreviewService._apply_loop_animation(
            text_clip, animations, width, height)

        return text_clip

    # 接收用户前端或者Make或者用户调用API 传递过来的 preTemplateJson 进行处理
    # 对应时序图的 生成preTemplate.json

    @staticmethod
    def generate_preview(preTemplateJson: Dict[str, Any]) -> Dict[str, Any]:
        # 解析配置
        canvas_config: Dict[str, Any] = preTemplateJson.get(
            'canvas_config', {})
        text_conf: Dict[str, Any] = preTemplateJson.get('text_conf', {})
        video_conf: Dict[str, Any] = preTemplateJson.get('video_conf', {})

        width = canvas_config.get('width', 1920)
        height = canvas_config.get('height', 1080)
        fps = canvas_config.get('fps', 30)

        # 创建存储目录 /backend/storage/previews
        storage_dir = os.path.join(os.path.dirname(
            os.path.dirname(__file__)), 'storage', 'previews')
        os.makedirs(storage_dir, exist_ok=True)

        # 生成输出文件路径 这里就是预览视频的输出位置
        output_filename = f"{uuid.uuid4()}.mp4"
        output_path = os.path.join(storage_dir, output_filename)

        try:
            # 处理视频素材
            clips, total_duration = VideoPreviewService._process_video_materials(
                video_conf, width, height)

            # 如果没有视频素材，创建一个黑色背景
            if not clips:
                text_duration_us = text_conf.get('duration', 8000000)
                total_duration = text_duration_us / TIME_BASE_CONVERSION

                # ColorClip 是创建一个黑色的背景的视频片段
                background = ColorClip(size=(width, height), color=(
                    0, 0, 0), duration=total_duration)
                clips.append(background)

            # 处理文本的相关配置
            if text_conf:
                text_clip, text_end_time = VideoPreviewService._create_text_clip(
                    text_conf, width, height)

                # 文本的持续时间
                text_duration = text_conf.get(
                    'duration', 8000000) / TIME_BASE_CONVERSION

                # 配置文本动画
                text_clip = VideoPreviewService._process_text_animations(
                    text_clip, text_conf, text_duration, width, height)

                clips.append(text_clip)
                total_duration = max(total_duration, text_end_time)

            # 合成最终视频
            final_clip = CompositeVideoClip(clips, size=(width, height))

            # 确保总时长足够
            if total_duration > 0:
                final_clip = final_clip.set_duration(total_duration)

            # 导出视频
            final_clip.write_videofile(
                output_path, fps=fps, codec='libx264', audio=False, threads=4)

            # 释放资源
            final_clip.close()
            for clip in clips:
                if hasattr(clip, 'close'):
                    clip.close()

            # 上传到S3
            s3_service = S3Service()
            with open(output_path, 'rb') as file_obj:
                object_key = f"previews/{output_filename}"
                upload_result = s3_service.upload_file(
                    file_obj=file_obj,
                    object_key=object_key,
                    content_type="video/mp4",
                    public=True
                )

            logger.info(f"✅ 预览视频已保存到本地: {output_path}")
            logger.info(f"📤 预览视频已上传到S3: {upload_result['file_url']}")

            return {
                'success': True,
                'message': '预览视频生成成功',
                'data': {
                    'video_url': upload_result['file_url'],
                    'object_key': upload_result['object_key'],
                    'local_path': output_path,
                    'duration': total_duration
                }
            }

        except Exception as e:
            # 清理临时文件
            try:
                if os.path.exists(output_path):
                    os.unlink(output_path)
            except:
                pass
            logger.error(f"视频预览生成失败: {str(e)}", exc_info=True)
            raise e
